import { director, ISchedulable, resources } from "cc";
import Notifier from "../notifer/Notifier";
import SocketManager from "./SocketManager";


export default class Socket implements ISchedulable {
    id?: string;
    uuid?: string;

    private _socket: WebSocket;
    private _isDebug: boolean;

    /** 连接超时时间 */
    private _connectTimeout: int;
    private _connectUrl: string;
    private _canReconnect: boolean;
    /** 是否正在发起连接 */
    private _isConnecting: boolean;
    public get isConnecting(): boolean { return this._isConnecting; }
    /** 最大尝试重连次数 */
    private _maxReconnectTimes: int;
    /** 当前重连次数 */
    private _reconectTimes: int;

    constructor() {
        this._isDebug = null;
        this._connectTimeout = 8;
        this._connectUrl = '';
        this._canReconnect = false;
        this._isConnecting = true;
        this._maxReconnectTimes = 10;
        this._reconectTimes = 0;
        this.id = 'Socket_ID';
        this.uuid = 'Socket_UUID';
    }

    /**
     * 连接socket
     * @param url  
     * @returns 
     */
    public connect(url: string): void {
        if (this._socket && this._socket.readyState == WebSocket.CONNECTING) {
            console.warn('socket is connecting');
            return;
        }
        this.clean();
        this._connectUrl = url;
        if (!url) {
            console.error('socket 还未建立');
            return;
        }

        director.getScheduler().unschedule(this.connectTimer, this);
        director.getScheduler().schedule(this.connectTimer, this, this._connectTimeout);

        this.printDebug('connect to '+ this._connectUrl);
        if (this.isAndroidNative()) {
            this.loadWssCacert().then((pemUrl) => {
                //@ts-ignore
                this._socket = new WebSocket(this._connectUrl, [], pemUrl);
                this._socket.binaryType = 'arraybuffer';
                this.registEvent();
                this._isConnecting = true;
            }).catch((err) => {
                this.printDebug(err);
            });

        } else {
            this._socket = new WebSocket(this._connectUrl);
            this._socket.binaryType = 'arraybuffer';
            this.registEvent();
            this._isConnecting = true;
        }
    }

    /** 主动尝试 重连 */
    public reconnectImmediately(): void{
        this.printDebug('主动开始重连');
        if(this._isConnecting) return;
        if(!this._canReconnect) return;

        Notifier.dispatch(EnumSocketEvent.SocketStartReconnect);
        this.connect(this._connectUrl);
    }

    public send(msgId: int, content: any, proto:IProto): void{
        let unit8 =  this.encode(content, proto);
        let buff =  unit8.slice().buffer;
        this._socket.send(buff);
    }   

    public isConnect(): boolean {
        if(this._socket && this._socket.readyState == WebSocket.OPEN) {
            return true;
        }
        return false;
    }

    /**
     * socket 重连
     */
    private reconnect(): void{
        if(this._reconectTimes < 1) {
            Notifier.dispatch(EnumSocketEvent.SocketTryConnect);
        }
        if(this._isConnecting) return;
        if(!this._canReconnect) return;

        this._reconectTimes ++;
        this.printDebug('Socket 尝试重连 第 '+ this._reconectTimes + ' 次');

        this.addOrRemoveDelayConnentTimer(true);
    }

    private delayConnect(): void {
        this.addOrRemoveDelayConnentTimer(false);
        this.connect(this._connectUrl);
    }

    private registEvent(): void {
        this._socket.onopen = this.onSocketOpen.bind(this);
        this._socket.onclose = this.onSocketClose.bind(this);
        this._socket.onmessage = this.onSocketMessage.bind(this);
        this._socket.onerror = this.onSocketError.bind(this);
    }

    private onSocketOpen(event: Event): void {
        this.printDebug('Socket Open');
        this._canReconnect = true;
        this._isConnecting = false;
        director.getScheduler().unschedule(this.connectTimer, this);
        Notifier.dispatch(EnumSocketEvent.SocketOpen);
    }

    private onSocketClose(event: CloseEvent): void {
        // this.clean();
        this.printDebug('Socket Close');
        this._canReconnect = true;
        this._isConnecting = false;
        Notifier.dispatch(EnumSocketEvent.SocketClose);
        
        director.getScheduler().unschedule(this.connectTimer, this);
        //自动尝试 重连 
        if(this._reconectTimes < this._maxReconnectTimes) {
            this.reconnect();
        }else{
            this._reconectTimes = 0;
            Notifier.dispatch(EnumSocketEvent.SocketTryConnectFail);
        }
    }

    private onSocketMessage(event: MessageEvent): void {
        let dataView = new DataView(event.data);
        if(dataView.byteLength <= 0) {
            this.printDebug('消息是空的');
            return;
        }
        let uint8Array = new Uint8Array(dataView.buffer, 0);
        let msgId = 0;
        let proto = SocketManager.instance.getProto(msgId);
        if(!proto) {
            console.warn("Not Found Proto ", msgId);
            return;
        }
        let msgData =  this.decode(uint8Array, proto);
        this.printDebug(Date.now() + "：SocketMessage  " + " msgId: "+ msgId + " data: " + JSON.stringify(msgData));
        Notifier.dispatch(msgId, msgData);
    }

    private onSocketError(event: Event): void {
        this._canReconnect = true;
        this._isConnecting = false;
        director.getScheduler().unschedule(this.connectTimer, this);

        //自动尝试 重连 
        if(this._reconectTimes < this._maxReconnectTimes) {
            this.reconnect();
        }else{
            this._reconectTimes = 0;
            Notifier.dispatch(EnumSocketEvent.SocketTryConnectFail);
        }
        
    }

    private loadWssCacert(): Promise<string> {
        return new Promise((resolve, reject) => {
            resources.load('ssl/cacert', (err, asset) => {
                if (err) {
                    reject('load wss cacert error ' + err.toString());
                    return;
                }
                resolve(asset.nativeUrl);
            })
        })

    }

    private isAndroidNative(): boolean {
        return false;
    }

    private connectTimer(): void {
        this.printDebug('connect timeout');
        director.getScheduler().unschedule(this.connectTimer, this);
        Notifier.dispatch(EnumSocketEvent.SocketTimeout);
    }

    private addOrRemoveDelayConnentTimer(add: boolean): void {
        if(add) {
            director.getScheduler().unschedule(this.delayConnect, this);
            director.getScheduler().schedule(this.delayConnect, this, 1);
        }else{
            director.getScheduler().unschedule(this.delayConnect, this);
        }
    }

    /**
     * 反序列化
     * @param proto 
     */
    private decode(buffer: ArrayBuffer,proto: IProto): any {
        let msg = proto.create(new Uint8Array(buffer));
        return msg;
    }

    /**
     * 序列化
     * @param data 
     * @param proto 
     * @returns 
     */
    private encode(data: any, proto:IProto): Uint8Array {
        let unit8 =  proto.encode(data).finish().slice();
        return unit8;
    }

    private clean(): void {
        if (this._socket) {
            this._socket.onopen = null;
            this._socket.onclose = null;
            this._socket.onmessage = null;
            this._socket.onerror = null;
            if (this._socket.readyState == WebSocket.OPEN) {
                this._socket.close();
            }
            this._socket = null;
        }
        this._canReconnect = false;
    }

    private printDebug(str: string): void {
        if (this._isDebug) {
            console.log("[Socket] Log ====== ", str);
        }
    }
}


export enum EnumSocketEvent {
    SocketOpen = 'SocketOpen',
    SocketMessage = 'SocketMessage',
    SocketClose = 'SocketClose',
    SocketError = 'SocketError',
    SocketTimeout = 'SocketTimeout',  // 连接超时了
    SocketTryConnect = 'SocketTryConnect',  // 开始尝试重连
    SocketTryConnectFail = "SocketTryConnectFail", //自动重连失败了
    SocketStartReconnect = "SocketStartReconnect"  //主动进入重连
}